package org.clickframes.plugins.jee6;

import java.io.File;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.clickframes.model.Action;
import org.clickframes.model.ActionType;
import org.clickframes.model.Appspec;
import org.clickframes.model.Entity;
import org.clickframes.model.EntityProperty;
import org.clickframes.model.FileInput;
import org.clickframes.model.Form;
import org.clickframes.model.InternalLink;
import org.clickframes.model.Link;
import org.clickframes.model.MultiDropdownInput;
import org.clickframes.model.Output;
import org.clickframes.model.OutputList;
import org.clickframes.model.Page;
import org.clickframes.model.PageParameter;
import org.clickframes.model.SingleUserInput;
import org.clickframes.plugins.ClickframesPlugin;
import org.clickframes.plugins.PluginContext;
import org.clickframes.plugins.jee6.PageEntity.Type;
import org.clickframes.plugins.jee6.factory.EJBCDIServiceFactory;
import org.clickframes.plugins.jee6.factory.JPA2EntityFactory;
import org.clickframes.plugins.properties.JEE6PropertiesFileCreator;
import org.clickframes.techspec.Context;
import org.clickframes.techspec.Techspec;

/**
 * Placeholder for future functionality.
 * 
 * @author steven
 * 
 */
public class Plugin extends ClickframesPlugin {

    @SuppressWarnings("unused")
    private final Log logger = LogFactory.getLog(getClass());
    /**
     * Where messages are written, such as form captions and validation
     * messages.
     */
    private static final String PROPERTIES_FILE = "src" + File.separator + "main" + File.separator + "resources"
            + File.separator + "messages.properties";

    // testing new properties file functionality.
    @Override
    public void execute(PluginContext pluginContext) {
        Appspec appspec = pluginContext.getAppspec();
        final File propertiesFileInTarget = new File(pluginContext.getTechspec().getOutputDirectory(), PROPERTIES_FILE);
        // the actual source file, not the one in target/clickframes-incoming
        final File propertiesFileInSrc = new File(pluginContext.getTechspec().getOutputDirectory().getParentFile()
                .getParentFile(), PROPERTIES_FILE);
        // prevents exceptions when user didn't use archetype
        propertiesFileInTarget.getParentFile().mkdirs();
        // Merge existing file if available.
        // TODO: Get a better variable name + explanation in here
        final File finalDestination = propertiesFileInSrc.exists() ? propertiesFileInSrc : propertiesFileInTarget;
        JEE6PropertiesFileCreator.writeToPropertiesFile(finalDestination, appspec);

    }

    // need to add special variables.
    @Override
    public void entityContext(Context entityContext, Entity entity) {
        // Java representation of entity. Encapsulates complex branching logic
        // that is coming in future versions.
        entityContext.put("entityJava", JPA2EntityFactory.createEntity(entity));
        entityContext.put("service", EJBCDIServiceFactory.createService(entity));
    }

    /**
     * Adds a variable called "queryString" in the link context for outputList
     * links
     */
    @Override
    public void outputListLinkContext(Context outputListLinkContext, Page page, OutputList outputList, Link link) {
        // null, or a query string starting with "?" for example "?id=foo"

        // only for internal links
        if (link.isInternal()) {
            InternalLink internalLink = (InternalLink) link;
            Page targetPage = internalLink.getPage();

            StringBuilder sb = new StringBuilder();
            int count = 0;
            for (PageParameter targetParameter : targetPage.getParameters()) {
                if (count == 0) {
                    sb.append("?");
                } else {
                    sb.append("&");
                }
                sb.append(targetParameter.getId() + "=");
                count++;

                // auto fill value if possible
                if (targetParameter.getEntityProperty() != null) {
                    boolean found = false;

                    // 0. The first thing to try would be against the entity
                    // referenced by the outputList
                    if (outputList.getEntity().getId().equals(targetParameter.getEntityProperty().getEntity().getId())) {
                        // the output matches the expected parameter
                        sb.append("#{" + outputList.getEntity().getId() + "."
                                + targetParameter.getEntityProperty().getId() + "}");

                        found = true;
                    }

                    // 1. search all outputs on current page
                    if (found) {
                        continue;
                    }
                    for (Output output : page.getOutputs()) {
                        // see if there is a matching output on this page
                        if (output.getEntity().getId().equals(targetParameter.getEntityProperty().getEntity().getId())) {
                            // the output matches the expected parameter
                            sb.append("#{" + page.getId() + "Controller." + output.getId() + "."
                                    + targetParameter.getEntityProperty().getId() + "}");

                            found = true;
                        }
                    }

                    // 2. if not found, search all parameters on this page
                    if (found) {
                        continue;
                    }
                    for (PageParameter currentParameter : page.getParameters()) {
                        if (currentParameter.getEntityProperty() != null
                                && currentParameter.getEntityProperty().equals(targetParameter.getEntityProperty())) {
                            sb.append("#{" + page.getId() + "Controller." + currentParameter.getId() + "}");

                            found = true;
                        }
                    }

                    // 3. if not found, search all forms on current page with
                    // semantics of update
                    if (found) {
                        continue;
                    }

                    for (Form form : page.getForms()) {
                        // if no entities ignore this form
                        if (form.getEntities().size() == 0) {
                            continue;
                        }

                        // if any action has semantics of "update"
                        List<Action> updateActions = form.getActionsOfType(ActionType.UPDATE);
                        if (updateActions.size() == 0) {
                            continue;
                        }

                        for (Entity formEntity : form.getEntities()) {
                            // see if there is a matching form entity on this
                            // page
                            if (formEntity.getId().equals(targetParameter.getEntityProperty().getEntity().getId())) {
                                // the form entity matches the expected
                                // parameter, good!

                                // now find the input which matches the entity
                                // property
                                SingleUserInput matchingInput = form.getInputFor(targetParameter.getEntityProperty());

                                sb.append("#{" + page.getId() + "Controller." + form.getId() + matchingInput.getName()
                                        + "}");

                                found = true;
                            }
                        }
                    }
                }
            }

            outputListLinkContext.put("queryString", sb.toString());
            link.getX().put("queryString", sb.toString());
        }
    }

    @Override
    public void inputContext(Context inputContext, Form form, SingleUserInput input) {
        if (form.isDefaultForm()) {
            input.getX().put("javaId", input.getId());
            input.getX().put("javaName", input.getName());
        } else {
            input.getX().put("javaId", form.getId() + input.getName());
            input.getX().put("javaName", form.getName() + input.getName());
        }

        {
            // java type
            String javaType;
            if (input instanceof FileInput) {
                javaType = "UploadedFile";
            } else if (input instanceof MultiDropdownInput) {
                javaType = "List<String>";
            } else {
                javaType = "String";
            }
            input.getX().put("javaType", javaType);
        }
    }

    @Override
    public void formActionContext(Context actionContext, Page page, Form form, Action action) {
        if (form.isDefaultForm()) {
            action.getX().put("javaId", action.getId());
            action.getX().put("javaName", action.getName());
        } else {
            action.getX().put("javaId", form.getId() + action.getName());
            action.getX().put("javaName", form.getName() + action.getName());
        }
    }

    @Override
    public void entityPropertyContext(Context entityPropertyContext, EntityProperty entityProperty) {
        String javaType = "String";

        Techspec techspec = (Techspec) entityPropertyContext.get("techspec");

        if (entityProperty.getType() != null) {
            switch (entityProperty.getType()) {
                case BOOLEAN:
                    javaType = "Boolean";
                    break;
                case DATE:
                    javaType = "java.util.Date";
                    break;
                case ENTITY:
                    javaType = entityProperty.getForeignEntity().getName();
                    break;
                case FILE:
                    javaType = techspec.getPackageName() + ".entity.UploadedFile";
                    break;
                case INT:
                    javaType = "Integer";
                    break;
                case TEXT:
                default:
                    break;
            }
        }

        if (entityProperty.isMultiple()) {
            javaType = "List<" + javaType + ">";
        }

        entityPropertyContext.put("javaType", javaType);
        entityProperty.getX().put("javaType", javaType);
    }

    @Override
    public void pageContext(Context pageContext, Page page) {
        {
            // page extension
            PageExtension pageExtension = new PageExtension(page);

            // enties for page parameters linked to primary keys
            for (PageParameter pageParameter : page.getParameters()) {
                if (pageParameter.getEntityProperty() != null) {
                    PageEntity pageEntity = PageEntity.create(pageParameter.getEntityProperty().getEntity());
                    pageEntity.setPageParameter(pageParameter);
                    if (pageParameter.getEntityProperty().isPrimaryKey()) {
                        pageEntity.setType(Type.PARAM_PRIMARY_KEY);
                    } else {
                        pageEntity.setType(Type.PARAM_OTHER);
                    }
                    pageExtension.getPageEntities().add(pageEntity);
                }
            }

            // entities linked to outputs with entity id equal to output id
            for (Output output : page.getOutputs()) {
                PageEntity pageEntity = PageEntity.create(output.getEntity());
                pageEntity.setType(Type.OUTPUT);
                pageEntity.setOutput(output);
                pageExtension.getPageEntities().add(pageEntity);
            }

            // consider all input entities
            for (Entity entity : page.getInputEntities()) {
                PageEntity pageEntity = PageEntity.create(entity);
                pageEntity.setType(Type.INPUT);
                pageExtension.getPageEntities().add(pageEntity);
            }

            pageExtension.analyzeAndSetPageEntityVariableNames();

            // all entity lists which need to be generated
            for (OutputList outputList : page.getOutputLists()) {
                PageEntityList pageEntityList = new PageEntityList();
                pageEntityList.setOutputList(outputList);
                pageEntityList.setId(outputList.getId());
                pageExtension.getPageEntityLists().add(pageEntityList);
            }

            page.getX().put("entities", pageExtension.getPageEntities());
            page.getX().put("outputLists", pageExtension.getPageEntityLists());
            page.getX().put("uniqueEntities", pageExtension.getUniqueEntities());
        }

        page.getX().put("javaId", page.getId() + "Page");
        page.getX().put("javaName", page.getName() + "Page");
        page.getX().put("pageLoadActionId", "processPageParameters");
    }
}